輸入為一個元素皆為整數的陣列,回傳陣列中最長的山 (peak) 的長度。山是指相鄰的元素嚴格遞增到一個值最大的元素,再嚴格遞減的情況。也就是一個山最少會由三個元素組成。
舉例來說,[1, 4, 10, 2] 為山,但 [4, 0, 1]、[1, 2, 2, 0]、[2, 4, 5] 都不算是山,因為沒有遞增到最高點或在高點之後並沒有遞減下來。
sample input:
array = [1, 2, 3, 3, 4, 0, 10, 6, 5, -1, -3, 2, 3]
sample output:
6 // 0, 10, 6, 5, -1, -3
一開始的想法可能會是遍歷陣列,同時記錄每個元素之間是增還是減,並以此來找出山的結構。但這樣的作法會有很多要考慮的細節,例如陣列一開始是遞增、遞減、持平的操作就有所不同,另外也要考慮在各種找到或沒找到山的情況持續更新記錄。
第二種想法是把問題拆解成兩個部分:(1) 找出所有的山,(2) 在其中找出最長的山。
(1) 找出所有的山:山的結構一定有山頂,所以可以理解為找到所有大於左右元素的元素。作法為遍歷陣列,從第二個元素看到倒數第二個元素 (因為高點不可能是第一和最後一個數字),比較每個元素和左右相鄰的元素,並記錄高點的位置。
以範例輸入來說,遍歷之後會找到兩個山的頂點,此時只是記錄位置,還沒有計算長度。
[1, 2, 3, 3, 4, 0, 10, 6, 5, -1, -3, 2, 3]
^ ^
(2) 比較長度:從山頂的位置開始,向左和向右檢查,持續記錄兩邊延續的長度,直到碰到不為遞減的元素或超出陣列範圍。
實作上可以再進一步將兩個部分合在一起完成。也就是找到為山頂的數字時,直接向左右計算長度。另外,向右遞減的範圍內也可以確定不可能出現其他山頂,所以計算完後可以直接從山之後的元素繼續。例如上方例子中,從 10 開始向左右檢查,右邊 6, 5, -1, -3 這段一定不會再出現其他高點,所以計算完之後可以從 2 開始繼續步驟。
n 為陣列長度,
time: O(n)
space: O(1)
程式碼是以 while 迴圈來遍歷陣列,步驟最後計算完長度後,可以依上述所說從山結束後的位置繼續進行 (i = rightIdx
的部分)。改成用 for 迴圈檢查每一個數字也是可行。
function longestPeak(array) {
let longestPeak = 0;
let i = 1
while (i < array.length - 1) {
let isPeak = array[i] > array[i - 1] && array[i] > array[i + 1];
if (!isPeak) {
i++;
continue;
}
// 向左右計算長度
let leftIdx = i - 2;
while (leftIdx >= 0 && array[leftIdx] < array[leftIdx + 1]) {
leftIdx--;
}
let rightIdx = i + 2;
while (rightIdx < array.length && array[rightIdx] < array[rightIdx - 1]) {
rightIdx++;
}
// 更新目前最長的山
let currentPeak = rightIdx - leftIdx - 1;
longestPeak = currentPeak > longestPeak ? currentPeak : longestPeak;
i = rightIdx;
}
return longestPeak;
}